
/*
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>


#define NGX_SYSLOG_MAX_STR                                  \
    NGX_MAX_ERROR_STR + sizeof("<255>Jan 01 00:00:00 ") - 1 \
        + (NGX_MAXHOSTNAMELEN - 1) + 1 /* space */          \
        + 32 /* tag */ + 2             /* colon, space */


static char     *ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer);
static ngx_int_t ngx_syslog_init_peer(ngx_syslog_peer_t *peer);
static void      ngx_syslog_cleanup(void *data);


static char *facilities[] = {"kern",     "user",   "mail",   "daemon", "auth",
                             "intern",   "lpr",    "news",   "uucp",   "clock",
                             "authpriv", "ftp",    "ntp",    "audit",  "alert",
                             "cron",     "local0", "local1", "local2", "local3",
                             "local4",   "local5", "local6", "local7", NULL};

/* note 'error/warn' like in nginx.conf, not 'err/warning' */
static char *severities[] = {"emerg",  "alert", "crit",  "error", "warn",
                             "notice", "info",  "debug", NULL};

static ngx_log_t   ngx_syslog_dummy_log;
static ngx_event_t ngx_syslog_dummy_event;


char *
ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
{
    ngx_pool_cleanup_t *cln;

    peer->facility = NGX_CONF_UNSET_UINT;
    peer->severity = NGX_CONF_UNSET_UINT;

    if (ngx_syslog_parse_args(cf, peer) != NGX_CONF_OK)
    {
        return NGX_CONF_ERROR;
    }

    if (peer->server.sockaddr == NULL)
    {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "no syslog server specified");
        return NGX_CONF_ERROR;
    }

    if (peer->facility == NGX_CONF_UNSET_UINT)
    {
        peer->facility = 23; /* local7 */
    }

    if (peer->severity == NGX_CONF_UNSET_UINT)
    {
        peer->severity = 6; /* info */
    }

    if (peer->tag.data == NULL)
    {
        ngx_str_set(&peer->tag, "nginx");
    }

    peer->conn.fd = (ngx_socket_t)-1;

    peer->conn.read  = &ngx_syslog_dummy_event;
    peer->conn.write = &ngx_syslog_dummy_event;

    ngx_syslog_dummy_event.log = &ngx_syslog_dummy_log;

    cln = ngx_pool_cleanup_add(cf->pool, 0);
    if (cln == NULL)
    {
        return NGX_CONF_ERROR;
    }

    cln->data    = peer;
    cln->handler = ngx_syslog_cleanup;

    return NGX_CONF_OK;
}


static char *
ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
{
    u_char    *p, *comma, c;
    size_t     len;
    ngx_str_t *value;
    ngx_url_t  u;
    ngx_uint_t i;

    value = cf->args->elts;

    p = value[1].data + sizeof("syslog:") - 1;

    for (;;)
    {
        comma = (u_char *)ngx_strchr(p, ',');

        if (comma != NULL)
        {
            len    = comma - p;
            *comma = '\0';
        }
        else
        {
            len = value[1].data + value[1].len - p;
        }

        if (ngx_strncmp(p, "server=", 7) == 0)
        {
            if (peer->server.sockaddr != NULL)
            {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "duplicate syslog \"server\"");
                return NGX_CONF_ERROR;
            }

            ngx_memzero(&u, sizeof(ngx_url_t));

            u.url.data     = p + 7;
            u.url.len      = len - 7;
            u.default_port = 514;

            if (ngx_parse_url(cf->pool, &u) != NGX_OK)
            {
                if (u.err)
                {
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "%s in syslog server \"%V\"", u.err,
                                       &u.url);
                }

                return NGX_CONF_ERROR;
            }

            peer->server = u.addrs[0];
        }
        else if (ngx_strncmp(p, "facility=", 9) == 0)
        {
            if (peer->facility != NGX_CONF_UNSET_UINT)
            {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "duplicate syslog \"facility\"");
                return NGX_CONF_ERROR;
            }

            for (i = 0; facilities[i] != NULL; i++)
            {
                if (ngx_strcmp(p + 9, facilities[i]) == 0)
                {
                    peer->facility = i;
                    goto next;
                }
            }

            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "unknown syslog facility \"%s\"", p + 9);
            return NGX_CONF_ERROR;
        }
        else if (ngx_strncmp(p, "severity=", 9) == 0)
        {
            if (peer->severity != NGX_CONF_UNSET_UINT)
            {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "duplicate syslog \"severity\"");
                return NGX_CONF_ERROR;
            }

            for (i = 0; severities[i] != NULL; i++)
            {
                if (ngx_strcmp(p + 9, severities[i]) == 0)
                {
                    peer->severity = i;
                    goto next;
                }
            }

            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "unknown syslog severity \"%s\"", p + 9);
            return NGX_CONF_ERROR;
        }
        else if (ngx_strncmp(p, "tag=", 4) == 0)
        {
            if (peer->tag.data != NULL)
            {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "duplicate syslog \"tag\"");
                return NGX_CONF_ERROR;
            }

            /*
             * RFC 3164: the TAG is a string of ABNF alphanumeric characters
             * that MUST NOT exceed 32 characters.
             */
            if (len - 4 > 32)
            {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "syslog tag length exceeds 32");
                return NGX_CONF_ERROR;
            }

            for (i = 4; i < len; i++)
            {
                c = ngx_tolower(p[i]);

                if (c < '0' || (c > '9' && c < 'a' && c != '_') || c > 'z')
                {
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "syslog \"tag\" only allows "
                                       "alphanumeric characters "
                                       "and underscore");
                    return NGX_CONF_ERROR;
                }
            }

            peer->tag.data = p + 4;
            peer->tag.len  = len - 4;
        }
        else if (len == 10 && ngx_strncmp(p, "nohostname", 10) == 0)
        {
            peer->nohostname = 1;
        }
        else
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "unknown syslog parameter \"%s\"", p);
            return NGX_CONF_ERROR;
        }

    next:

        if (comma == NULL)
        {
            break;
        }

        p = comma + 1;
    }

    return NGX_CONF_OK;
}


u_char *
ngx_syslog_add_header(ngx_syslog_peer_t *peer, u_char *buf)
{
    ngx_uint_t pri;

    pri = peer->facility * 8 + peer->severity;

    if (peer->nohostname)
    {
        return ngx_sprintf(buf, "<%ui>%V %V: ", pri, &ngx_cached_syslog_time,
                           &peer->tag);
    }

    return ngx_sprintf(buf, "<%ui>%V %V %V: ", pri, &ngx_cached_syslog_time,
                       &ngx_cycle->hostname, &peer->tag);
}


void
ngx_syslog_writer(ngx_log_t *log, ngx_uint_t level, u_char *buf, size_t len)
{
    u_char            *p, msg[NGX_SYSLOG_MAX_STR];
    ngx_uint_t         head_len;
    ngx_syslog_peer_t *peer;

    peer = log->wdata;

    if (peer->busy)
    {
        return;
    }

    peer->busy     = 1;
    peer->severity = level - 1;

    p        = ngx_syslog_add_header(peer, msg);
    head_len = p - msg;

    len -= NGX_LINEFEED_SIZE;

    if (len > NGX_SYSLOG_MAX_STR - head_len)
    {
        len = NGX_SYSLOG_MAX_STR - head_len;
    }

    p = ngx_snprintf(p, len, "%s", buf);

    (void)ngx_syslog_send(peer, msg, p - msg);

    peer->busy = 0;
}


ssize_t
ngx_syslog_send(ngx_syslog_peer_t *peer, u_char *buf, size_t len)
{
    ssize_t n;

    if (peer->conn.fd == (ngx_socket_t)-1)
    {
        if (ngx_syslog_init_peer(peer) != NGX_OK)
        {
            return NGX_ERROR;
        }
    }

    /* log syslog socket events with valid log */
    peer->conn.log = ngx_cycle->log;

    if (ngx_send)
    {
        n = ngx_send(&peer->conn, buf, len);
    }
    else
    {
        /* event module has not yet set ngx_io */
        n = ngx_os_io.send(&peer->conn, buf, len);
    }

    if (n == NGX_ERROR)
    {
        if (ngx_close_socket(peer->conn.fd) == -1)
        {
            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
                          ngx_close_socket_n " failed");
        }

        peer->conn.fd = (ngx_socket_t)-1;
    }

    return n;
}


static ngx_int_t
ngx_syslog_init_peer(ngx_syslog_peer_t *peer)
{
    ngx_socket_t fd;

    fd = ngx_socket(peer->server.sockaddr->sa_family, SOCK_DGRAM, 0);
    if (fd == (ngx_socket_t)-1)
    {
        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
                      ngx_socket_n " failed");
        return NGX_ERROR;
    }

    if (ngx_nonblocking(fd) == -1)
    {
        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
                      ngx_nonblocking_n " failed");
        goto failed;
    }

    if (connect(fd, peer->server.sockaddr, peer->server.socklen) == -1)
    {
        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
                      "connect() failed");
        goto failed;
    }

    peer->conn.fd = fd;

    /* UDP sockets are always ready to write */
    peer->conn.write->ready = 1;

    return NGX_OK;

failed:

    if (ngx_close_socket(fd) == -1)
    {
        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
                      ngx_close_socket_n " failed");
    }

    return NGX_ERROR;
}


static void
ngx_syslog_cleanup(void *data)
{
    ngx_syslog_peer_t *peer = data;

    /* prevents further use of this peer */
    peer->busy = 1;

    if (peer->conn.fd == (ngx_socket_t)-1)
    {
        return;
    }

    if (ngx_close_socket(peer->conn.fd) == -1)
    {
        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
                      ngx_close_socket_n " failed");
    }
}
